<?php

namespace app\controller\api;

use app\BaseController;
use app\common\Code;
use app\model\UserModel;
use think\facade\Cache;
use think\facade\Request;

/**
 * 微信小程序授权登录控制器
 */
class Wechat extends BaseController
{
    // 微信小程序配置 - 请在配置文件中设置这些参数
    private string $appid = '';      // 小程序 AppID
    private string $secret = '';     // 小程序 AppSecret

    public function initialize()
    {
        parent::initialize();
        // 从配置文件读取
        $this->appid = config('wechat.mini_program.appid', '');
        $this->secret = config('wechat.mini_program.secret', '');
    }

    /**
     * 微信小程序登录
     * 前端调用 wx.login() 获取 code，传递给后端
     */
    public function login()
    {
        $code = Request::param('code', '');

        if (empty($code)) {
            fail([], '缺少code参数', Code::ERROR_400);
        }

        // 1. 通过 code 换取 session_key 和 openid
        $url = "https://api.weixin.qq.com/sns/jscode2session";
        $params = [
            'appid' => $this->appid,
            'secret' => $this->secret,
            'js_code' => $code,
            'grant_type' => 'authorization_code'
        ];

        $result = $this->httpGet($url, $params);
        $data = json_decode($result, true);

        if (isset($data['errcode']) && $data['errcode'] != 0) {
            fail([], '微信登录失败：' . ($data['errmsg'] ?? '未知错误'), Code::ERROR);
        }

        $openid = $data['openid'] ?? '';
        $sessionKey = $data['session_key'] ?? '';
        $unionid = $data['unionid'] ?? '';  // 如果开发者拥有多个移动应用、网站应用和公众帐号，可通过unionid来区分用户的唯一性

        if (empty($openid) || empty($sessionKey)) {
            fail([], '获取用户信息失败', Code::ERROR);
        }

        // 2. 将 session_key 缓存起来，用于后续解密手机号等敏感数据
        $sessionId = md5($openid . time() . rand(1000, 9999));
        Cache::set(Code::CACHE_WECHAT_SESSION . $sessionId, [
            'openid' => $openid,
            'session_key' => $sessionKey,
            'unionid' => $unionid,
        ], Code::CACHE_WECHAT_SESSION_TTL); // 缓存2小时

        // 3. 根据 openid 查询数据库，判断用户是否已注册
        // TODO: 查询数据库，检查用户是否存在
        $user = UserModel::getUserInfo(['openid' => $openid]);
        $token = api_token($unionid);
        if (empty($user)) {
            $userId = UserModel::setInsert(['openid' => $openid, 'unionid' => $unionid, 'token' => $token, 'login_ip' => Request::ip()]);
        } else {
            $userId = $user->id;
            UserModel::update(['id' => $userId], ['token' => $token]);
        }

        // 5. 返回数据给前端
        success([
            'session_id' => $sessionId,  // 返回给前端，用于后续接口调用
            'openid' => $openid,
            'unionid' => $unionid,
            'user_id' => $userId,      // 数据库用户ID
            'token' => $token,          // 自己系统的登录token
            'is_new_user' => empty($user), // 是否新用户
        ], '登录成功', Code::SUCCESS);
    }

    /**
     * 获取微信绑定的手机号
     * 需要用户在小程序端点击按钮授权
     * 前端使用 <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">
     *
     * 中间件已验证 session_id，可直接从 request 中获取 wechatSession
     */
    public function getPhoneNumber()
    {
        $code = Request::param('code', '');  // 手机号获取凭证，从前端 getPhoneNumber 回调获取

        if (empty($code)) {
            fail([], '缺少code参数', Code::ERROR_400);
        }

        // 从 request 中获取 session 信息（中间件已注入）
        $session = Request::instance()->wechatSession;

        // 获取access_token
        $accessToken = $this->getAccessToken();
        if (!$accessToken) {
            fail([], '获取access_token失败', Code::ERROR);
        }

        // 通过 code 获取手机号
        $url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" . $accessToken;
        $postData = json_encode(['code' => $code]);

        $result = $this->httpPost($url, $postData);
        $data = json_decode($result, true);

        if (isset($data['errcode']) && $data['errcode'] != 0) {
            fail([], '获取手机号失败：' . ($data['errmsg'] ?? '未知错误'), Code::ERROR);
        }

        $phoneInfo = $data['phone_info'] ?? [];
        $phoneNumber = $phoneInfo['phoneNumber'] ?? '';  // 用户绑定的手机号（国外手机号会有区号）
        $purePhoneNumber = $phoneInfo['purePhoneNumber'] ?? '';  // 没有区号的手机号
        $countryCode = $phoneInfo['countryCode'] ?? '';  // 区号

        // TODO: 更新用户手机号到数据库
        UserModel::setUpdate(['openid' => $session['openid'],], ['phone' => $purePhoneNumber, 'country_code' => $countryCode]);

        success([
            'phone_number' => $phoneNumber,           // 完整手机号，包含区号
            'pure_phone_number' => $purePhoneNumber,  // 纯手机号，不含区号
            'country_code' => $countryCode,           // 区号
        ], '获取手机号成功', Code::SUCCESS);
    }

    /**
     * 获取用户信息（头像、昵称等）
     * 注意：2021年4月后，小程序getUserProfile接口获取用户信息需用户主动触发
     * 前端调用 wx.getUserProfile() 获取用户信息后传给后端
     *
     * 中间件已验证 session_id，可直接从 request 中获取 wechatSession
     */
    public function getUserInfo()
    {
        $encryptedData = Request::param('encrypted_data', '');
        $iv = Request::param('iv', '');

        // 或者直接接收前端已解密的用户信息
        $nickName = Request::param('nick_name', '');//昵称
        $avatarUrl = Request::param('avatar_url', '');//头像
        $gender = Request::param('gender', 0);// 0-未知，1-男，2-女
        $province = Request::param('province', '');//省份
        $city = Request::param('city', '');//城市

        // 从 request 中获取 session 信息（中间件已注入）
        $session = Request::instance()->wechatSession;

        $userInfo = [];

        // 方式1：如果前端传了加密数据，需要解密
        if (!empty($encryptedData) && !empty($iv)) {
            $sessionKey = $session['session_key'];
            $userInfo = $this->decryptData($encryptedData, $iv, $sessionKey);

            if (!$userInfo) {
                fail([], '解密用户信息失败', Code::ERROR);
            }
        } // 方式2：前端直接传明文数据（推荐，因为小程序端可以直接获取）
        else {
            $userInfo = [
                'nickName' => $nickName,
                'avatarUrl' => $avatarUrl,
                'gender' => $gender,
                'province' => $province,
                'city' => $city,
            ];
        }

        // TODO: 更新用户信息到数据库
        userModel::setUpdate(['openid' => $session['openid'],], [
            'nickname' => $userInfo['nickName'] ?? '',
            'avatar' => $userInfo['avatarUrl'] ?? '',
            'gender' => $userInfo['gender'] ?? 0,
            'province' => $userInfo['province'] ?? '',
            'city' => $userInfo['city'] ?? '',
        ]);

        success([
            'openid' => $session['openid'],
            'user_info' => $userInfo,
        ], '获取用户信息成功', Code::SUCCESS);
    }

    /**
     * 一键授权登录（整合版）
     * 前端一次性传递所有信息
     */
    public function quickAuth()
    {
        $code = Request::param('code', '');
        $nickName = Request::param('nick_name', '');
        $avatarUrl = Request::param('avatar_url', '');
        $gender = Request::param('gender', 0);

        if (empty($code)) {
            fail([], '缺少code参数', Code::ERROR_400);
        }

        // 1. 获取 openid 和 session_key
        $url = "https://api.weixin.qq.com/sns/jscode2session";
        $params = [
            'appid' => $this->appid,
            'secret' => $this->secret,
            'js_code' => $code,
            'grant_type' => 'authorization_code'
        ];

        $result = $this->httpGet($url, $params);
        $data = json_decode($result, true);

        if (isset($data['errcode']) && $data['errcode'] != 0) {
            fail([], '微信登录失败：' . ($data['errmsg'] ?? '未知错误'), Code::ERROR);
        }

        $openid = $data['openid'] ?? '';
        $sessionKey = $data['session_key'] ?? '';
        $unionid = $data['unionid'] ?? '';

        if (empty($openid)) {
            fail([], '获取openid失败', Code::ERROR);
        }

        // 2. 缓存 session_key
        $sessionId = md5($openid . time() . rand(1000, 9999));
        //$sessionId
        Cache::set(Code::CACHE_WECHAT_SESSION . $sessionId, [
            'openid' => $openid,
            'session_key' => $sessionKey,
            'unionid' => $unionid,
        ], Code::CACHE_WECHAT_SESSION_TTL);


        $token = api_token($openid);
        // TODO: 数据库操作
        $user = UserModel::getUserInfo(['openid' => $openid]);
        if (empty($user)) {
            $userId = UserModel::setInsert([
                'openid' => $openid,
                'unionid' => $unionid,
                'nickname' => $nickName,
                'avatar' => $avatarUrl,
                'gender' => $gender,
                'token' => $token
            ]);
        } else {
            $userId = $user->id;
            $user = [
                'nickname' => $nickName,
                'avatar' => $avatarUrl,
                'gender' => $gender,
                'token' => $token
            ];
            UserModel::setUpdate(['id' => $userId], $user);
        }

        success([
            'session_id' => $sessionId,
            'openid' => $openid,
            'user_data' => $user,  // 完整的用户数据，可直接保存到数据库
            'token' => $token,
            'is_new_user' => empty($isNewUser),
        ], '授权成功', Code::SUCCESS);
    }

    /**
     * 获取微信 Access Token
     * 建议缓存，每天限制调用次数
     *
     * @return string|false
     */
    private function getAccessToken()
    {
        // 先从缓存读取
        $accessToken = Cache::get(Code::CACHE_WECHAT_ACCESS_TOKEN);
        if ($accessToken) {
            return $accessToken;
        }

        // 缓存不存在，重新获取
        $url = "https://api.weixin.qq.com/cgi-bin/token";
        $params = [
            'grant_type' => 'client_credential',
            'appid' => $this->appid,
            'secret' => $this->secret,
        ];

        $result = $this->httpGet($url, $params);
        $data = json_decode($result, true);

        if (isset($data['access_token'])) {
            $accessToken = $data['access_token'];
            $expiresIn = $data['expires_in'] ?? 7200;
            // 缓存 access_token，提前5分钟过期
            Cache::set(Code::CACHE_WECHAT_ACCESS_TOKEN, $accessToken, $expiresIn - 300);
            return $accessToken;
        }
        return false;
    }

    /**
     * 解密微信加密数据
     *
     * @param string $encryptedData 加密数据
     * @param string $iv 初始向量
     * @param string $sessionKey 会话密钥
     * @return array|false
     */
    private function decryptData(string $encryptedData, string $iv, string $sessionKey)
    {
        $aesKey = base64_decode($sessionKey);
        $aesIV = base64_decode($iv);
        $aesCipher = base64_decode($encryptedData);

        $result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, OPENSSL_RAW_DATA, $aesIV);

        if (!$result) {
            return false;
        }

        $dataObj = json_decode($result, true);

        if (!$dataObj) {
            return false;
        }

        // 验证appid
        if (isset($dataObj['watermark']['appid']) && $dataObj['watermark']['appid'] != $this->appid) {
            return false;
        }

        return $dataObj;
    }

    /**
     * GET 请求
     */
    private function httpGet(string $url, array $params = []): string
    {
        if (!empty($params)) {
            $url .= '?' . http_build_query($params);
        }

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        $result = curl_exec($ch);
        curl_close($ch);

        return $result ?: '';
    }

    /**
     * POST 请求
     */
    private function httpPost(string $url, string $data): string
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Content-Length: ' . strlen($data)
        ]);

        $result = curl_exec($ch);
        curl_close($ch);

        return $result ?: '';
    }
}